package service

import (
	sqlc "bgs/db/sqlc"
	"bgs/service/model"
	"bgs/util"
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"time"
)

// EnsureActivityUserProcedure 确保ActivityUserProcedure对于一个活动+一个用户始终有一条记录
func (s *ActivityServiceImp) EnsureActivityUserProcedure(ctx context.Context, q *sqlc.Queries, withLock bool, activityID int64, userID int64) (activityUserProcedure sqlc.ActivityUserProcedure, isCreated bool, err error) {
	slog := util.NewContextLogger(ctx, s.slog)

	var sqlErr error
	if withLock {
		activityUserProcedure, sqlErr = q.GetActivityUserProcedureByActivityIDAndUserIDToLock(ctx, sqlc.GetActivityUserProcedureByActivityIDAndUserIDToLockParams{
			ActivityID: activityID,
			UserID:     userID,
		})
	} else {
		activityUserProcedure, sqlErr = q.GetActivityUserProcedureByActivityIDAndUserID(ctx, sqlc.GetActivityUserProcedureByActivityIDAndUserIDParams{
			ActivityID: activityID,
			UserID:     userID,
		})
	}

	if sqlErr != nil {
		if sql.ErrNoRows != sqlErr {
			err = sqlErr
			return
		}
		slog.Infof("找不到活动用户过程记录")
		cActivity := s.cacher.FetchActivity(activityID)
		if cActivity == nil {
			err = fmt.Errorf("not found activity:%d", activityID)
			return
		}

		activityUserProcedure, err = q.UpsertActivityUserProcedure(ctx, sqlc.UpsertActivityUserProcedureParams{
			BrandID:          cActivity.BrandID,
			ActivityID:       activityID,
			RootActivityID:   sqlc.NInt64FromInt64(cActivity.RootID),
			ParentActivityID: sqlc.NInt64FromInt64(cActivity.ParentID),
			UserID:           userID,
		})
		if err != nil {
			return
		}

		if cActivity.Type == string(sqlc.EmActivityTypeLottery1) {

			// 加一层保险判断cActivity.MaxiumPurchaseFrequency必须>0
			if cActivity.MaxiumPurchaseFrequency == 0 {
				err = fmt.Errorf("lottery1 activity MaxiumPurchaseFrequency cache value is zero:%d", cActivity.MaxiumPurchaseFrequency)
				return
			}

			// var distributions []model.PrizeDistribution
			// 奖品分布目前从活动定义里读取,不记录在过程中,节省空间
			// distributions := cActivity.TypeInfoPtr.Lottery1Ptr.Distributions

			procedureExtInfoPtr := &model.ActivityUserProcedureExtInfo{
				Lottery1: model.Lottery1Procedure{
					AvailableCount: uint32(cActivity.MaxiumPurchaseFrequency),
					DrawedCount:    0,
					// Distributions:  distributions,
				},
			}
			procedureExtInfoBytes, marshalErr := json.Marshal(procedureExtInfoPtr)
			if marshalErr != nil {
				err = marshalErr
				return
			}
			activityUserProcedure, err = q.UpdateActivityUserProcedureExtInfo(ctx, sqlc.UpdateActivityUserProcedureExtInfoParams{
				ID:      activityUserProcedure.ID,
				ExtInfo: sqlc.NRawMessageFromRawMessage(json.RawMessage(procedureExtInfoBytes)),
			})
			if err != nil {
				return
			}
		}

		isCreated = true
	}
	slog.Infof("确保活动用户过程记录并返回 ok")
	return
}

type UpdateActivityUserProcedureSinceActivityUserInterativeParam struct {
	ActivityPtr                    *sqlc.Activity
	ActivityUserProcedureLockedPtr *sqlc.ActivityUserProcedure
}

// UpdateActivityUserProcedureSinceCheckIn1 签到1更新活动用户过程
func (s *ActivityServiceImp) UpdateActivityUserProcedureSinceCheckIn1(ctx context.Context, q *sqlc.Queries, p UpdateActivityUserProcedureSinceActivityUserInterativeParam) (userAwards []*model.ActivityUserAwardExtInfo, err error) {
	slog := util.NewContextLogger(ctx, s.slog)

	// activityID := p.ActivityPtr.ID
	userID := p.ActivityUserProcedureLockedPtr.UserID
	// activityUserProcedureLocked, err := s.EnsureActivityUserProcedure(ctx, q, true, activityID, userID)
	// if err != nil {
	// 	return
	// }
	// slog.Infof("1、签到1-确保获取锁定的活动用户过程记录(%d)(%d) ok", activityID, userID)

	typeInfoPtr := &model.ActivityTypeInfo{}
	json.Unmarshal(p.ActivityPtr.TypeInfo.RawMessage, typeInfoPtr)
	continuous := typeInfoPtr.CheckIn1Ptr.MaxContinuous
	slog.Infof("2、获取当前[签到1]活动的最大连签数量(%d) ok", continuous)

	procedureExtInfoPtr := &model.ActivityUserProcedureExtInfo{}
	json.Unmarshal(p.ActivityUserProcedureLockedPtr.ExtInfo.RawMessage, procedureExtInfoPtr)
	continuousCheckInDate := procedureExtInfoPtr.CheckIn1.LastCheckInDate
	continuousCheckInCount := procedureExtInfoPtr.CheckIn1.ContinuousCheckInCount
	slog.Infof("3、获取当前[签到1]活动用户过程中的连签数量(%d) ok", continuousCheckInCount)

	now := time.Now()
	today := util.GoDateToString(now)
	if today == continuousCheckInDate {
		err = fmt.Errorf("今日已签到")
		return
	}
	isContinuous := util.GoDateToString(now.AddDate(0, 0, -1)) == continuousCheckInDate
	procedureExtInfoPtr.CheckIn1.LastCheckInDate = today
	if !isContinuous {
		slog.Infof("4、连签中断")
		procedureExtInfoPtr.CheckIn1.ContinuousCheckInCount = 1
	} else {
		// 这里的连签既包含7天内的,也包含拿到连签奖励后被设置为0,又重现开始新一轮连签的
		procedureExtInfoPtr.CheckIn1.ContinuousCheckInCount = continuousCheckInCount + 1
		slog.Infof("4、连签+1")
	}

	userAwards, err = s.CreateUserAwardsSinceCheckIn1(ctx, q, CreateUserAwardsSinceCheckIn1Param{
		ActivityPtr:          p.ActivityPtr,
		UserID:               userID,
		CheckIn1InfoPtr:      typeInfoPtr.CheckIn1Ptr,
		CheckIn1ProcedurePtr: &procedureExtInfoPtr.CheckIn1,
	})
	if err != nil {
		return
	}
	slog.Infof("5、创建中奖记录 ok")

	procedureExtInfoBytes, marshalErr := json.Marshal(procedureExtInfoPtr)
	if marshalErr != nil {
		err = marshalErr
		return
	}
	_, err = q.UpdateActivityUserProcedureExtInfo(ctx, sqlc.UpdateActivityUserProcedureExtInfoParams{
		ID:      p.ActivityUserProcedureLockedPtr.ID,
		ExtInfo: sqlc.NRawMessageFromRawMessage(json.RawMessage(procedureExtInfoBytes)),
	})
	if err != nil {
		return
	}

	slog.Infof("6、更新[签到1]活动用户过程 ok")

	slog.Infof("签到1更新活动用户过程 ok")
	return
}

/*
1.中奖率分三档：难：10%、中：50%、易：90%

2.奖品按照顺序从1到7分配权重，一等奖为1，二等奖为2，三等奖为3,以此类推

3.计算公式为：（当前奖品权重/全部奖品权重之和）*中奖概率，保留小数后一位（多余直接去尾）
*/

// UpdateActivityUserProcedureSinceLottery1 抽奖1更新活动用户过程(抽奖过程)
func (s *ActivityServiceImp) UpdateActivityUserProcedureSinceLottery1(ctx context.Context, q *sqlc.Queries, p UpdateActivityUserProcedureSinceActivityUserInterativeParam) (userAwards []*model.ActivityUserAwardExtInfo, lottery1PrecedureInExtPtr *model.Lottery1Procedure, err error) {
	slog := util.NewContextLogger(ctx, s.slog)

	activityID := p.ActivityPtr.ID
	userID := p.ActivityUserProcedureLockedPtr.UserID
	// activityUserProcedureLocked, err := s.EnsureActivityUserProcedure(ctx, q, true, activityID, userID)
	// if err != nil {
	// 	return
	// }
	// slog.Infof("1、抽奖1-确保获取锁定的活动用户过程记录(%d)(%d) ok", activityID, userID)

	procedureExtInfoPtr := &model.ActivityUserProcedureExtInfo{}
	json.Unmarshal(p.ActivityUserProcedureLockedPtr.ExtInfo.RawMessage, procedureExtInfoPtr)
	availableCount := procedureExtInfoPtr.Lottery1.AvailableCount
	drawedInfos := procedureExtInfoPtr.Lottery1.DrawedInfos
	drawedCount := procedureExtInfoPtr.Lottery1.DrawedCount

	typeInfoPtr := &model.ActivityTypeInfo{}
	json.Unmarshal(p.ActivityPtr.TypeInfo.RawMessage, typeInfoPtr)
	// distributions := procedureExtInfoPtr.Lottery1.Distributions
	distributions := typeInfoPtr.Lottery1Ptr.Distributions

	slog.Infof("3、获取当前[抽奖1]活动用户过程中的可抽奖数(%d)和已抽奖数(%d) ok", availableCount, drawedCount)

	// if maxiumCount == 0 {
	// 	maxiumCount = uint32(activityPtr.MaxiumPurchaseFrequency.Int32)
	// }
	// ep:将来可以动态更新最大抽奖次数

	if availableCount <= 0 {
		err = fmt.Errorf("抽奖次数已用完")
		return
	}

	// 正式开始抽奖
	now := time.Now()
	drawedNumber := uint32(util.RandomInt(1, 1000))
	drawedAt := util.GoTimeToStringf(now, util.LayoutSecond)
	drawedInfo := &model.Lottery1ProcedureDrawedInfo{
		DrawedNumber: drawedNumber,
		DrawedAt:     drawedAt,
	}
	var winPrizeDistributionPtr *model.PrizeDistribution
	for _, item := range distributions {
		if item.Available && util.UInt32Contains(item.Wins, drawedNumber) {
			// 中奖
			winPrizeDistributionPtr = &item
			break
		}
	}

	if winPrizeDistributionPtr != nil {

		// 抽中
		drawedInfo.DrawedWin = true
		drawedInfo.Prize = winPrizeDistributionPtr.Prize

		normalActivityProductWithProductInfos, normalActivitySkus, fetchErr := s.FetchNormalProductAndSkus(ctx, q, activityID)
		if fetchErr != nil {
			err = fetchErr
			return
		}

		winActivityProduct := normalActivityProductWithProductInfos[winPrizeDistributionPtr.Prize-1]
		winProductID := winActivityProduct.ProductID
		activitySkusWithLock, sqlErr := q.ListActivitySkusByActivityIDAndProductIDToLock(ctx, sqlc.ListActivitySkusByActivityIDAndProductIDToLockParams{
			ActivityID: activityID,
			ProductID:  winProductID,
		})
		if sqlErr != nil {
			err = sqlErr
			return
		}

		// 查找抽奖资格
		maximum := util.FoldLeftInt32(util.ToGenericSlice(activitySkusWithLock), func(remain int32, v interface{}) int32 {
			val := v.(sqlc.ActivitySku)
			return val.Maximum
		}, 0)

		wined, aggrErr := s.AggrRealPrizeProductCount(ctx, q, activityID, winProductID)
		if aggrErr != nil {
			err = aggrErr
			return
		}

		remain := maximum - int32(wined)
		if remain <= 0 {
			slog.Infof("抽奖奖品资格不足(%d)", winProductID)
			// 将库存不足的distribution更新
			winPrizeDistributionPtr.Available = false

			// 觉得procedureExtInfoPtr内部winPrizeDistributionPtr应该指向相同地址,但需要验证,下面是验证代码
			slog.Infof("winPrizeDistributionPtr.Available:%d, %v", winPrizeDistributionPtr.Prize, winPrizeDistributionPtr.Available)
			for _, dis := range distributions {
				if dis.Prize == winPrizeDistributionPtr.Prize {
					slog.Infof("procedureExtInfoPtr.Lottery1.Distributions:%d, %v", dis.Prize, dis.Available)
					dis.Available = false
					typeInfoBytes, marshalErr := json.Marshal(typeInfoPtr)
					if marshalErr != nil {
						err = marshalErr
						return
					}
					err = q.UpdateActivityOfBTypeInfo(ctx, sqlc.UpdateActivityOfBTypeInfoParams{
						ID:       activityID,
						TypeInfo: sqlc.NRawMessageFromRawMessage(json.RawMessage(typeInfoBytes)),
					})
					if err != nil {
						return
					}
					break
				}
			}

			slog.Infof("抽奖1抽中(%d)等奖但是库存不足,不创建中奖记录,对外提示还是未中奖 ok", winPrizeDistributionPtr.Prize)
		} else {
			userAwards, err = s.CreateUserAwardsSinceLottery1(ctx, q, CreateUserAwardsSinceLottery1Param{
				ActivityPtr:             p.ActivityPtr,
				UserID:                  userID,
				WinActivityProductPtr:   &winActivityProduct,
				WinPrizeDistributionPtr: winPrizeDistributionPtr,
				ActivitySkus:            normalActivitySkus,
			})
			if err != nil {
				return
			}
			slog.Infof("抽奖1抽中(%d)等奖并且库存充足 ok", winPrizeDistributionPtr.Prize)
		}
	}

	drawedInfos = append(drawedInfos, drawedInfo)

	// 更新抽奖过程
	procedureExtInfoPtr.Lottery1.DrawedInfos = drawedInfos
	procedureExtInfoPtr.Lottery1.AvailableCount -= 1
	procedureExtInfoPtr.Lottery1.DrawedCount += 1
	procedureExtInfoBytes, marshalErr := json.Marshal(procedureExtInfoPtr)
	if marshalErr != nil {
		err = marshalErr
		return
	}
	_, err = q.UpdateActivityUserProcedureExtInfo(ctx, sqlc.UpdateActivityUserProcedureExtInfoParams{
		ID:      p.ActivityUserProcedureLockedPtr.ID,
		ExtInfo: sqlc.NRawMessageFromRawMessage(json.RawMessage(procedureExtInfoBytes)),
	})
	if err != nil {
		return
	}
	lottery1PrecedureInExtPtr = &procedureExtInfoPtr.Lottery1
	slog.Infof("抽奖1更新活动用户过程(抽奖过程) ok")
	return
}
